-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extended partial methods #43582
Extended partial methods #43582
Conversation
4b81c69
to
4d66859
Compare
4d66859
to
3d99838
Compare
src/Compilers/CSharp/Test/Symbol/Symbols/ExtendedPartialMethodsTests.cs
Outdated
Show resolved
Hide resolved
At this point I believe that 'new' should not be allowed when there is no implementation part. It just seems so confusing to shadow a method with a partial that has no implementation, and then erase the call sites. |
Please add a PROTOTYPE to note this, or an issue in the running spec and we can follow up with LDM later. |
</data> | ||
<data name="ERR_PartialMethodWithExtendedModMustHaveImplementation" xml:space="preserve"> | ||
<value>Partial method {0} must have an implementation part because it has a 'virtual', 'override', 'sealed', or 'new', or 'extern' modifier.</value> | ||
</data> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the LDM discussions we should not have separate errors for these violations. The requirement for an implementation is dictated by the presence of an explicit accessibility modifier. That is what mandates their is an implementation, not everything else.
At the same time all of these new scenarios must be predicated on an explicit accessibility modifier. Basically if you see out
that is illegal unless there is an explicit accessibility modifier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
<value>Both partial method declarations must be 'override' or neither may be 'override'.</value> | ||
</data> | ||
<data name="ERR_PartialMethodSealedDifference" xml:space="preserve"> | ||
<value>Both partial method declarations must be 'sealed' or neither may be 'sealed'.</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider just grouping all of these messages into one message. Essentially
Both partial method declarations must agree on 'override', 'virtiual', 'sealed' and accessibility
Listing out every single modifier means we have to add a new message here every time we add a feature to the langauge which impacts method signatures.
Will defer to others here if they feel there is more precedent in the other direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the existing diagnostics have a distinct error code for 'static' mismatch, 'unsafe' mismatch and 'readonly' mismatch, but I am fine with grouping these together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
{ | ||
if (!method.ReturnType.IsVoidType()) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_PartialMethodWithNonVoidReturnMustHaveImplementation, method.Locations[0], method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only illegal if the partial
method lacks an explicit accessibility modifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
} | ||
else if (method.Parameters.Any(p => p.RefKind == RefKind.Out)) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_PartialMethodWithOutParamMustHaveImplementation, method.Locations[0], method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only illegal if the partial
method lacks an explicit accessibility modifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
} | ||
else if (method.HasExtendedPartialModifier) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_PartialMethodWithExtendedModMustHaveImplementation, method.Locations[0], method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only illegal if the partial
method lacks an explicit accessibility modifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, sounds like these diagnostics will change from "MustHaveImplementation" to "MustHaveAccessibility".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
@@ -637,6 +641,15 @@ internal bool IsPartialWithoutImplementation | |||
} | |||
} | |||
|
|||
internal sealed override bool IsNew |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why this is needed? My mental model is that we already error when partial
methods differ an the modifier new
. Hence here we're attempting to change the IsNew
return to account for illegal code. Is this meant to make the IDE experience cleaner in the face of errors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I have just run myself in a circle on this. My intention was to propose making this legal to support a scenario where a user declares a partial stub which hides a base member, and the generator that produces the implementation doesn't have to think about whether to copy over the 'new' modifier or not. I'll just revert this part.
One question I have: the precedent for misuse of 'new' is to give a warning, not an error. So should the mismatch of 'new' modifiers between parts be a warning or an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we will simply require identical combinations of 'new' modifiers (giving an error on mismatch). Can revisit if there is a compelling reason to.
@@ -756,7 +769,9 @@ internal override bool IsExpressionBodied | |||
get { return _isExpressionBodied; } | |||
} | |||
|
|||
private DeclarationModifiers MakeModifiers(SyntaxTokenList modifiers, MethodKind methodKind, bool hasBody, Location location, DiagnosticBag diagnostics, out bool modifierErrors) | |||
internal bool HasExplicitAccessMod { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use Modifier
instead of abbreviating as Mod
in API surface area.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
src/Compilers/CSharp/Test/Symbol/Symbols/ExtendedPartialMethodsTests.cs
Outdated
Show resolved
Hide resolved
Diagnostic(ErrorCode.ERR_FeatureInPreview, "M1").WithArguments("extended partial methods").WithLocation(9, 32)); | ||
|
||
comp = CreateCompilation(text1, parseOptions: TestOptions.RegularWithExtendedPartialMethods); | ||
comp.VerifyDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test that verifies the DllImportAttribute
is emitted correctly in this scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also verify the IL for a call to M1()
to ensure the call is emitted.
In reply to: 417430836 [](ancestors = 417430836)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
comp.VerifyDiagnostics(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel like there are several categories of tests missing here. Apologies if I read past them:
-
partial
methods implementinginterface
members -
partial
methods whichoverride
members -
partial
methods which are bothoverride
and have nullability attributes like[MaybeNull]
which impact our OHI checks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have converted your bullet points to checkboxes :)
src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs
Outdated
Show resolved
Hide resolved
82ec2d5
to
fa9532e
Compare
src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs
Show resolved
Hide resolved
After examining the existing behavior for 'async partial' methods it feels like the declaration part of a method should not have 'IsExtern == true' just because the implementation uses the 'extern' keyword. So I will revert that bit of the change+tests. |
No, but |
} | ||
} | ||
"; | ||
// PROTOTYPE: should it be allowed to specify duplicate nullability attributes? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should it be allowed to specify duplicate nullability attributes? [](start = 26, length = 65)
If we allow nullability attributes on both parts, we should allow attributes on both parts for all attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is feeling like maybe we should just ship the current rules and see how people feel about it. Adding special allowances for duplicate attributes in partials seems like something you can't just implement speculatively, we have to see how people end up using/experiencing it.
// (21,25): error CS0111: Type 'I1' already defines a member called 'M9' with the same parameter types | ||
// static partial void M9(); | ||
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "M9").WithArguments("M9", "I1").WithLocation(21, 25), | ||
// (22,32): error CS8796: Partial method 'I1.M10()' must have accessibility modifiers because it has a 'virtual', 'override', 'sealed', or 'new', or 'extern' modifier. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or 'new', or 'extern' [](start = 152, length = 21)
Minor: It looks like there a number of comments with the previous error text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@@ -1682,10 +1682,13 @@ private void CheckMemberNameConflicts(DiagnosticBag diagnostics) | |||
// UNDONE: Consider adding a secondary location pointing to the second method. | |||
private void ReportMethodSignatureCollision(DiagnosticBag diagnostics, SourceMemberMethodSymbol method1, SourceMemberMethodSymbol method2) | |||
{ | |||
// Partial methods are allowed to collide by signature. | |||
if (method1.IsPartial && method2.IsPartial) | |||
switch (method1, method2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switch (method1, method2) [](start = 12, length = 25)
Does this switch
statement represent a change in behavior from the original if
?
Unless there is a change in behavior, we should stick with the if
statement since it is significantly simpler. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this fixes #43883. This solution ends up creating additional diagnostics in some error scenarios. I tried moving the checks for PartialMethodOnlyOneLatent/PartialMethodOnlyOneActual in here, but because partial method merging already happened in an earlier step, it causes us to miss diagnostics for multiple "actual" methods.
If we could do this at the same time or right before we merge partial methods, we might be able to get higher quality diagnostics in some scenarios when members have conflicting signatures:
- if the methods are not 'partial', we report
ErrorCode.ERR_MemberAlreadyExists
- if we detect that both methods are partial implementations, we give
ErrorCode.ERR_PartialMethodOnlyOneActual
- if we detect that both methods are partial definitions, we give
ErrorCode.ERR_PartialMethodOnlyOneLatent
It might also be possible to fix this by modifying the partial method signature comparer, that groups declarations that might be part of the same partial method, to be aware of RefKind differences.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This solution ends up creating additional diagnostics in some error scenarios.
That sounds ok. We can revisit this later if necessary.
In reply to: 418977822 [](ancestors = 418977822)
// public delegate System.Collections.Generic.IAsyncEnumerable<int> Delegate([EnumeratorCancellation] CancellationToken token); // 4 | ||
Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("token").WithLocation(15, 80), | ||
// (16,62): error CS0766: Partial methods must have a void return type | ||
// (16,62): error CS9051: Partial method 'C2.M(CancellationToken)' must have accessibility modifiers because it has a non-void return type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CS9051 [](start = 34, length = 6)
There are a number of instances of CS9051.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs
Show resolved
Hide resolved
parseOptions: TestOptions.RegularWithExtendedPartialMethods, | ||
expectedOutput: "1"); | ||
verifier.VerifyDiagnostics(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} [](start = 8, length = 1)
Consider testing access outside the type hierarchy as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more accessibility tests.
@jaredpar please let me know if I have addressed all your comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some more test suggestions but overall looking good.
} | ||
|
||
[Fact] | ||
public void Override_AllowNull_02() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is there a test where we have a override partial
which involves [AllowNull]
that succeeds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. Override_AllowNull_02
has the expected combination of [AllowNull]
given on the parameters of the override, and it warns on dereference of the parameters. That seems successful.
comp.VerifyDiagnostics(); | ||
} | ||
|
||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider an override
test where the types change from generic parameters to actual types. Example:
class Base<T> {
protected virtual void M(T p) { }
}
partial class Derived : Base<string> {
protected override partial void M(string p);
protected override partial void M(string p) { }
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding to the test plan and will address in a follow-up PR.
Test plan: #43795
Fixes #43883 (see test
ConflictingOverloads_RefOut_02
)This PR enables 'out' parameters, non-'void' returns, accessibility modifiers, and several other modifiers to be used with 'partial' methods.
Draft spec: dotnet/csharplang#3417